Once you have your environment configured and know how to develop an application with it, it's time to make your own project. In this lesson you will create all the entities required for your project in order to get your application working.
By now I assume that you know how to create a new entity with Eclipse and how to run the application, because you have already read Lesson1: Getting started, right?
Basic domain model
First, we'll create the entities for your Invoicing application. The domain model is rather basic, but enough to learn a lot of interesting things:
We'll start with six classes. Later on we'll add a few more to it. Remember that you already have an initial version of Customer and Product.
Reference (ManyToOne) as descriptions list (combo)
Let's start with the most simple case. We are going to create a Category entity and associate it to the Product, displaying it as a combo.
The code for Category entity is:
packageorg.openxava.invoicing.model;importjavax.persistence.*;importorg.hibernate.annotations.GenericGenerator;importorg.openxava.annotations.*;
@Entitypublicclass Category {
@Id
@Hidden // The property is not shown to the user. It's an internal identifier
@GeneratedValue(generator="system-uuid")// Universally Unique Identifier (1)
@GenericGenerator(name="system-uuid", strategy = "uuid")
@Column(length=32)privateString oid;
@Column(length=50)privateString description;// REMEMBER TO GENERATE THE GETTERS AND SETTERS FOR THE ABOVE FIELDS}
It only has an identifier and a description property. In this case we use the Universally Unique Identifier (shown as 1) algorithm to generate the id. The advantage of this id generator is that you can migrate your application to another database (DB2, MySQL, Oracle, Informix, etc) without touching your code. The other id generators of JPA rely on the database to generate the id thereby making them not so portable as UUID.
Although we have not included the getters and setters in the above code snippet, the getters and setter must be included in the code. Fortunately Eclipse can write them for us. First choose Source > Generate Getters and Setters... from the Eclipse menu:
Then click on Select All and then OK to have the getters and setters in your code:
Remember to do this every time you create a new entity or add a new field to an existing one. This utility is also available with the right mouse
button on the entity code, or Ctrl-1 on a field.
Execute the Category module and add some categories: Now, we'll associate Product with Category: Add the next category reference declaration in your Product entity:
publicclass Product {
...
@ManyToOne(// The reference is persisted as a database relationship
fetch=FetchType.LAZY, // The reference is loaded on demand
optional=true)// The reference can have no value
@DescriptionsList // Thus the reference is displayed using a comboprivate Category category;// A regular Java reference// REMEMBER TO GENERATE THE GETTER AND SETTER FOR category}
This is a plain JPA many-to-one relationship, the one that you can learn more about in appendix B. In this case, thanks to the @DescriptionsList annotation it is displayed as a combo:
Now it's time to complete your Product entity.
Stereotypes
Product entity at least needs to have attributes such as price, etc. Also it would be nice to have photos and a field for remarks. We are going to use stereotypes to do that. A stereotype specifies a special use of a type. For example, you can use String to store names, comments or identifiers, and you can use a BigDecimal to store percentages, money or quantities. Different uses of stereotypes is to mark this specific use.
The best way to understand what a stereotype is, is to see it in action. Let's add price, photo, morePhotos and remarks properties to your Product entity:
@Stereotype("MONEY")// The price property is used to store moneyprivateBigDecimal price;// Include the import java.math.* BigDecimal is typically used for money
@Stereotype("PHOTO")// The user can view and change a photo
@Column(length=16777216)// This size to store big photosprivatebyte[] photo;
@Stereotype("IMAGES_GALLERY")// A complete image gallery is available
@Column(length=32)// The 32 length string is for storing the key of the galleryprivateString morePhotos;
@Stereotype("MEMO")// This is for a big text, a text area or equivalent will be usedprivateString remarks;// REMEMBER TO GENERATE THE GETTERS AND SETTERS FOR THE ABOVE FIELDS
Note that you have to add the import java.math.* to use BigDecimal. Eclipse helps you to add imports, just put the cursor at the end of the type, at the end of BigDecimal in this case, and press Ctrl - Space Bar.
You have seen how to use stereotypes. Now you only have to use the name of the stereotype and OpenXava will apply special treatment. Execute the module for the Product now, and you will see:
As you can see, each stereotype produces an effect in the user interface. Stereotypes have effects in sizes, validations, editors, etc. Stereotypes also allow you to reuse built-in functionality easily. For example, with a mere marking of a simple string property as @Stereotype(“IMAGES_GALLERY”) you have a full image gallery available. Click on the camera of the morePhotos property and you'll see the gallery:
Apart from these, OpenXava has many other useful built in stereotypes such as LABEL, BOLD_LABEL, TIME, DATETIME, ZEROS_FILLED, HTML_TEXT, IMAGE_LABEL, EMAIL, TELEPHONE, WEBURL, IP, ISBN, CREDIT_CARD, EMAIL_LIST, PASSWORD, MAC, FILE, FILES, ICON, etc.
Now you have Product ready to use. Let's refine the Customer now.
Embeddable
We are going to add Address to our until now pretty naked Customer. The customer address is not shared by other customers, and when the customer is removed his address is removed too. Therefore we'll model the address concept as an embeddable class in this case. You can learn more about this in appendix B.
Add the Address class to your project:
packageorg.openxava.invoicing.model;importjavax.persistence.*;
@Embeddable // We use @Embeddable instead of @Entitypublicclass Address {
@Column(length = 30)// The members are annotated as in entity caseprivateString street;
@Column(length = 5)privateint zipCode;
@Column(length = 20)privateString city;
@Column(length = 30)privateString state;// REMEMBER TO GENERATE THE GETTERS AND SETTERS FOR THE ABOVE FIELDS}
You can see how the regular class has been annotated as @Embeddable. Its properties are annotated in the same way as entities, though embeddable classes do not support all functionality of entities.
Now, you can use Address in any entity. Just add a reference to your Customer entity:
publicclass Customer {
...
@Embedded // This is the way to reference an embeddable classprivate Address address;// A regular Java referencepublic Address getAddress(){if(address == null) address = new Address();// Thus it never is nullreturn address;}publicvoid setAddress(Address address){this.address = address;}}
The Address data is stored in the same table as the Customer data. And from a user interface perspective you have a frame around address. If you do not like the frame you only have to annotate the reference with @NoFrame thus:
@Embedded @NoFrame // With @NoFrame no frame is shown for addressprivate Address address;
Here's the user interface for an embedded reference with and without @NoFrame:
Now that we have the basic entities running, it's time to face the core entity of your application, the Invoice entity. Let's start with it step by step.
Composite key
We are not going to use a composite key for Invoice. It's better to avoid the use of composite keys. You always have the option to use an auto generated hidden id. Although, sometimes you may need to connect to a legacy database or maybe the schema design has been done by someone that likes composite keys, and you must use composite keys albeit it's not the best choice. Therefore, here you will learn how to use a composite key, though at the end we'll change it to a single auto generated id.
Let's start with a simple version of Invoice entity:
packageorg.openxava.invoicing.model;importjava.util.*;importjavax.persistence.*;importorg.openxava.annotations.*;
@Entity
@IdClass(InvoiceKey.class)// The id class has all the key properties (1)publicclass Invoice {
@Id // Though we have id class it still needs to be marked as @Id (2)
@Column(length = 4)privateint year;
@Id // Though we have id class it still needs to be marked as @Id (2)
@Column(length = 6)privateint number;
@Required
privateDate date;
@Stereotype("MEMO")privateString remarks;// REMEMBER TO GENERATE THE GETTERS AND SETTERS FOR THE ABOVE FIELDS}
If you want to use year and number as a composite key for your Invoice, a way to do so is by marking year and number as @Id (shown as 2), and also to have an id class (shown as 1). The id class must have year and number as properties. You can see InvoiceKey here:
packageorg.openxava.invoicing.model;publicclass InvoiceKey implements java.io.Serializable{// The key class must be serializableprivateint year;// It contains the properties marked ...privateint number;// ... as @Id in the entitypublicboolean equals(Object obj){// It must define equals methodif(obj == null)returnfalse;return obj.toString().equals(this.toString());}publicint hashCode(){// It must define hashCode methodreturn toString().hashCode();}publicString toString(){return"InvoiceKey::" + year + ":" + number;}// REMEMBER TO GENERATE THE GETTERS AND SETTERS FOR year AND number}
This code shows some of the requirements of a primary key class, such as to be serializable and to implement hashCode() and equals(). Eclipse can generate them for you with Source > Generate hashCode and equals()...
You already know how to use a composite key, and given that we have control over our schema, in the end we are going to use a UUID identifier for Invoice. Rewrite the Invoice entity to leave it as shown:
packageorg.openxava.invoicing.model;importjava.util.*;importjavax.persistence.*;importorg.hibernate.annotations.GenericGenerator;// ADD THIS IMPORTimportorg.openxava.annotations.*;
@Entity// @IdClass(InvoiceKey.class) // REMOVE THE @IdClass ANNOTATIONpublicclass Invoice {// ADD THE oid PROPERTY
@Id
@GeneratedValue(generator="system-uuid")
@Hidden
@GenericGenerator(name="system-uuid", strategy="uuid")
@Column(length=32)privateString oid;// REMEMBER TO GENERATE THE GETTER AND SETTER FOR oid// @Id // REMOVE THE @Id ANNOTATION
@Column(length=4)privateint year;// @Id // REMOVE THE @Id ANNOTATION
@Column(length=6)privateint number;
...
}
Also delete the InvoiceKey class. Using a hidden auto-generated key for Invoice class has several practical advantages over a composite key: You do not have to write the boring InvoiceKey class, you can modify the invoice number without losing any association from other objects and you can store in the same table the orders and invoices with the year/number repeated.
The code you have is enough to run the Invoice module. Do it and add some invoices if you want. Still a lot of work is remaining to be done in Invoice, like the default values for year, number and date.
Calculating default values
Currently the user needs to type the year, number and date in order to enter an invoice. It would be nice to have default values for them. It's easy to do it using the @DefaultValueCalculator annotation. In the next code you see how we can add the default values for year and date:
@Column(length=4)
@DefaultValueCalculator(CurrentYearCalculator.class)// Current yearprivateint year;
@Required
@DefaultValueCalculator(CurrentDateCalculator.class)// Current dateprivateDate date;
Also you have to add the next import at the top of your class:
importorg.openxava.calculators.*;
From now on when the user clicks on the 'new' button the year field will have the current year, and the date field the current date. These two calculators (CurrentYearCalculator and CurrentDateCalculator) are included in OpenXava. You can explore the org.openxava.calculators package to see other useful built-in calculators.
But sometimes you need your own logic for calculating the default value. For example, for number we want to add one to the last invoice number in the same year. Creating your own calculator with your logic is easy. First, create a package for calculators and call it org.openxava.invoicing.calculators. Then create in it a NextNumberForYearCalculator class, with the next code:
packageorg.openxava.invoicing.calculators;importjavax.persistence.*;importorg.openxava.calculators.*;importorg.openxava.jpa.*;publicclass NextNumberForYearCalculator implements ICalculator {// A calculator must implement ICalculatorprivateint year;// This value will be injected (using its setter) before calculatingpublicObject calculate()throwsException{// It does the calculationQuery query = XPersistence.getManager()// A JPA query
.createQuery("select max(i.number) from Invoice i" + " where i.year = :year");// The query returns// the max invoice number of the indicated year
query.setParameter("year", year);// We use the injected year as a parameter for the queryInteger lastNumber = (Integer) query.getSingleResult();return lastNumber == null?1 : lastNumber + 1;// Returns the last invoice number// of the year + 1 or 1 if there is no last number}publicint getYear(){return year;}publicvoid setYear(int year){this.year = year;}}
Your calculator must implement ICalculator interface (and therefore have a calculate() method). We declare a year property to put in the year of the calculation. To implement the logic we use a JPA query. You can learn how to use JPA in appendix B. Now we only have to annotate the number property in the Invoice entity:
@Id @Column(length=6)
@DefaultValueCalculator(value=NextNumberForYearCalculator.class,
properties=@PropertyValue(name="year")// To inject the value of year from Invoice to// the calculator before calling to calculate())privateint number;
Remember to add the import:
importorg.openxava.invoicing.calculators.*;
In this case you see something new, an annotation @PropertyValue. By using this annotation you're saying that the value of year property of the current Invoice will be moved to the property year of the calculator before doing the calculation. Now when ever the user clicks on 'new' the next invoice number is available for the year field. The way of calculating the invoice number is not the best for many concurrent users adding invoices. Don't worry, we'll improve this issue later on.
This is the visual effect of the default value calculators:
Default values are only the initial values. The user can change them if he wishes to.
Regular reference (ManyToOne)
Now that we have all atomic properties ready to use it's time to add relationships with other entities. We'll begin adding a reference from Invoice to Customer, because an invoice without customer is not very useful. Before adding the customer use the Invoice module to remove all the current invoices because we're going to make the customer required, so the old data could fail.
Add the next code to the Invoice entity.
@ManyToOne(fetch=FetchType.LAZY, optional=false)// Customer is requiredprivate Customer customer;// REMEMBER TO GENERATE THE GETTER AND SETTER FOR customer
Nothing more is required. The Invoice module is now like this one:
There is no more work left here now. Let's add the collection of details to your Invoice.
Collection of dependent objects
Usually an invoice needs to have a couple of lines with the details of products, quantities, etc. These details are part of the invoice. They are not shared with other invoices, and when an invoice is deleted its details are also deleted. So, the more natural way of modeling the invoice details is to use a collection of embeddable objects. To do it with JPA, declare a regular collection annotated with @ElementCollection:
@ElementCollection
privateCollection<Detail> details;// REMEMBER TO GENERATE THE GETTER AND SETTER FOR details
Using @ElementCollection when the invoice is removed its details are removed too. The details are not saved in the database until the invoice is saved and they are saved all at once.
In order to make this collection work you need to write the Detail class:
packageorg.openxava.invoicing.model;importjavax.persistence.*;
@Embeddable
publicclass Detail {privateint quantity;
@ManyToOne(fetch = FetchType.LAZY, optional = true)private Product product;// REMEMBER TO GENERATE THE GETTERS AND SETTERS FOR THE ABOVE FIELDS}
Note that Detail is annotated with @Embeddable not with @Entity, you cannot define an @ElementCollection of entities. This @Embeddable class can contain properties and references but not collections.
At the moment we only have quantity and product and that is enough to get the Invoice running with details. The user can add, edit and remove elements from the collection just as in a spreadsheet:
This screenshot emphasizes that the properties to show by default in a collection are the plain ones, that is the properties of references are not included by default. This fact produces an ugly user interface for our collection of invoice details, because only the quantity property is shown. You can fix it using @ListProperties, in this way:
As you can see, you only have to feed the value for the annotation @ListProperties with the list of the properties you wish, separated by commas. You can use qualified properties, that is, to use the dot notation for accessing properties of references, such as product.number and product.description in this case. The visual result is:
Refining the user interface
Congratulations! You have finished your domain model classes, and you have an application running. Now the user can work with products, categories, customers and even create invoices. In the case of products, categories and customers the user interface is pretty decent, though the user interface for invoices still can be improved a little.
By the way, you already have used some OpenXava annotations for refining the presentation, such as @DescriptionsList, @NoFrame and @ListProperties. In this section we'll use more of these annotations to give the Invoice user interface a better look without too much effort. Default user interface
This is the default user interface for Invoice:
As you can see, OpenXava shows all the members, one per row, in the order you have declared them in the source code. Also, you can see how in the case of the customer reference the default view of the Customer is created.
We are going to do some improvements. First, we'll define the layout of the members explicitly. In this way we can put year, number and date in the same row. Second, we are going to use a simpler view for Customer. The user does not need to see all the data of the customer when he is entering the invoice.
Using @View for defining layout
For defining the layout of Invoice members in the user interface you have to use the @View annotation. It is easy because you only have to enumerate the members to be shown. Look at the code:
@View(members= // This view has no name, so it will be the view used by default"year, number, date;" + // Comma separated means in the same line"customer;" + // Semicolon means a new line"details;" +
"remarks")publicclass Invoice {
At the end, we show all the members of Invoice, but we use commas to separate year, number and date. Thus they are in the same line, producing a more compact user interface, as this:
Using @ReferenceView to refine the user interface for reference
You still need to refine the way the customer reference is displayed, because it displays all the members of Customer, and for entering data for an Invoice, a simpler view of the customer may be better. To do so, you have to define a Simple view in Customer, and then indicate in Invoice that you want to use the Simple view of Customer to display it.
First, let's define the Simple view in Customer:
@View(name="Simple", // This view is used only when “Simple” is specified
members="number, name"// Shows only number and name in the same line)publicclass Customer {
When a view has a name, as in this case, then that view is only used when that name is specified. That is, though Customer has only this @View annotation, when you try to display a Customer it will not use this Simple view, but the one generated by default. If you define a @View with no name, that view will be the default one, though that is not the case here.
Now, you have to indicate that the reference to Customer from Invoice must use this Simple view. This is done by means of @ReferenceView:
@ManyToOne(fetch=FetchType.LAZY, optional=false)
@ReferenceView("Simple")// The view named 'Simple' is used to display this referenceprivate Customer customer;
Really simple, you only have to indicate the name of the view of the referenced entity you want to use.
After this the customer reference will be shown in a more compact way:
You can see below how you have refined your Invoice interface.
Refined user interface
This is the result of our refinements in the Invoice user interface:
You have seen how easy it is to use @View and @ReferenceView to get a more compact user interface for Invoice.
Now you have a user interface good enough to start working, and you really have done little work to get it going.
Agile development
Nowadays agile development is no longer a “new and breaking technique”, but an established way to do software development, even the ideal way to go for many people.
If you are not familiar with agile development you can have a look at www.agilemanifesto.org. Basically, agile development encourages the use of feedback from a working product over a careful upfront design. This gives a more prominent role to programmers and users, and minimizes the importance of analysts and software architects.
This type of development also needs a different type of tools. Because you need a working application rapidly. It must be as rapid to develop the initial application as it would be writing the functional description. Moreover, you need to respond to the user feedback quickly. The user needs to see his proposals running in short time.
OpenXava is ideal for agile development because not only does it allow a very rapid initial development, but it also allows you to make changes and see the effects instantly. Let's see a little example of this.
For example, once the user has looked at your application and starts to play with it, he takes into account that he works with books, music, software and so on. All these products have an author, and it would be useful to store the author, and see products by author.
Adding this new feature to your application is simple and rapid. First, create a new class for Author, with this:
packageorg.openxava.invoicing.model;importjavax.persistence.*;importorg.hibernate.annotations.GenericGenerator;importorg.openxava.annotations.*;
@Entitypublicclass Author {
@Id @GeneratedValue(generator="system-uuid") @Hidden
@GenericGenerator(name="system-uuid", strategy = "uuid")
@Column(length=32)privateString oid;
@Column(length=50) @Required
privateString name;// REMEMBER TO GENERATE THE GETTERS AND SETTERS FOR THE ABOVE FIELDS}
Now, add the next code to the existing Product entity.
@ManyToOne(fetch=FetchType.LAZY)
@DescriptionsList
private Author author;// REMEMBER TO GENERATE THE GETTER AND SETTER FOR author
Thus, your Product entity has a reference to Author. Remember to generate the setter and setter for author.
Really you have written a little amount of code. In order to see the effect, you only need to build your project (just press Ctrl-B in your Eclipse), which is just immediate. Then go to the browser and reload the page with the Product module, and you will see there, a combo for choosing the author of the product, just as you see here:
What if the user wants to choose an author and see all his products? Well. This is plain vanilla. You only have to make the relationship between Product and Author bidirectional. Go to the Author class and add the next code:
@OneToMany(mappedBy="author")
@ListProperties("number, description, price")privateCollection<Product> products;// REMEMBER TO GENERATE THE GETTER AND SETTER FOR products
To use Collection you have to add the next import:
importjava.util.*;
Remember to generate the getter and setter for products. Now, press Ctrl+B (to build) and refresh the browser with Author module. Choose an author and you will see his products. You have to see something like this:
Yes, you added a new collection, refreshed your browser and there you get the full user interface to manage it. In this case when the user click on Add button he will be able to choose a book from a list of all existing books, moreover when the author is removed his books are still there in the database. You can define another behavior with cascade as REMOVE or ALL, thus:
In this way when your user try to add a new book a dialog will be open to entry all the data for the book and with a save button. Moreover, when the author would be removed his books will be removed too. For the author/books case we don't want this behavior, but it can be useful in many cases where the @ElementCollection is not enough. In this section you have the complete code and steps required to do changes and see the result in the most interactive way. You have seen how OpenXava is an agile tool, ideal for doing agile development.
Summary
In this lesson you have learned how to use simple Java classes to create a Java Web application. With a mere few Java classes required to define your domain, you have a running application. Also, you have learned how to refine the default user interface using some OpenXava annotations. If you want to know all possible ways of refining the user interface with OpenXava look at the reference guide.
Yes! Now you have a working application with little effort. Although this application “as is” can be useful as a CRUD utility or a prototype, you still need to add validations, business logic, user interface behavior, security and so on in order to convert these entities you have written into a business application ready for your user.
You will learn all these advanced topics in the forthcoming lessons.
Table of Contents
Lesson 2: Modeling with Java
Once you have your environment configured and know how to develop an application with it, it's time to make your own project. In this lesson you will create all the entities required for your project in order to get your application working.By now I assume that you know how to create a new entity with Eclipse and how to run the application, because you have already read Lesson1: Getting started, right?
Basic domain model
First, we'll create the entities for your Invoicing application. The domain model is rather basic, but enough to learn a lot of interesting things:We'll start with six classes. Later on we'll add a few more to it. Remember that you already have an initial version of Customer and Product.
Reference (ManyToOne) as descriptions list (combo)
Let's start with the most simple case. We are going to create a Category entity and associate it to the Product, displaying it as a combo.The code for Category entity is:
It only has an identifier and a description property. In this case we use the Universally Unique Identifier (shown as 1) algorithm to generate the id. The advantage of this id generator is that you can migrate your application to another database (DB2, MySQL, Oracle, Informix, etc) without touching your code. The other id generators of JPA rely on the database to generate the id thereby making them not so portable as UUID.
Although we have not included the getters and setters in the above code snippet, the getters and setter must be included in the code. Fortunately Eclipse can write them for us. First choose Source > Generate Getters and Setters... from the Eclipse menu:
Then click on Select All and then OK to have the getters and setters in your code:
Remember to do this every time you create a new entity or add a new field to an existing one. This utility is also available with the right mouse
button on the entity code, or Ctrl-1 on a field.
Execute the Category module and add some categories:
Now, we'll associate Product with Category: Add the next category reference declaration in your Product entity:
This is a plain JPA many-to-one relationship, the one that you can learn more about in appendix B. In this case, thanks to the @DescriptionsList annotation it is displayed as a combo:
Now it's time to complete your Product entity.
Stereotypes
Product entity at least needs to have attributes such as price, etc. Also it would be nice to have photos and a field for remarks. We are going to use stereotypes to do that. A stereotype specifies a special use of a type. For example, you can use String to store names, comments or identifiers, and you can use a BigDecimal to store percentages, money or quantities. Different uses of stereotypes is to mark this specific use.The best way to understand what a stereotype is, is to see it in action. Let's add price, photo, morePhotos and remarks properties to your Product entity:
Note that you have to add the import java.math.* to use BigDecimal. Eclipse helps you to add imports, just put the cursor at the end of the type, at the end of BigDecimal in this case, and press Ctrl - Space Bar.
You have seen how to use stereotypes. Now you only have to use the name of the stereotype and OpenXava will apply special treatment. Execute the module for the Product now, and you will see:
As you can see, each stereotype produces an effect in the user interface. Stereotypes have effects in sizes, validations, editors, etc. Stereotypes also allow you to reuse built-in functionality easily. For example, with a mere marking of a simple string property as @Stereotype(“IMAGES_GALLERY”) you have a full image gallery available. Click on the camera of the morePhotos property and you'll see the gallery:
Apart from these, OpenXava has many other useful built in stereotypes such as LABEL, BOLD_LABEL, TIME, DATETIME, ZEROS_FILLED, HTML_TEXT, IMAGE_LABEL, EMAIL, TELEPHONE, WEBURL, IP, ISBN, CREDIT_CARD, EMAIL_LIST, PASSWORD, MAC, FILE, FILES, ICON, etc.
Now you have Product ready to use. Let's refine the Customer now.
Embeddable
We are going to add Address to our until now pretty naked Customer. The customer address is not shared by other customers, and when the customer is removed his address is removed too. Therefore we'll model the address concept as an embeddable class in this case. You can learn more about this in appendix B.Add the Address class to your project:
You can see how the regular class has been annotated as @Embeddable. Its properties are annotated in the same way as entities, though embeddable classes do not support all functionality of entities.
Now, you can use Address in any entity. Just add a reference to your Customer entity:
The Address data is stored in the same table as the Customer data. And from a user interface perspective you have a frame around address. If you do not like the frame you only have to annotate the reference with @NoFrame thus:
Here's the user interface for an embedded reference with and without @NoFrame:
Now that we have the basic entities running, it's time to face the core entity of your application, the Invoice entity. Let's start with it step by step.
Composite key
We are not going to use a composite key for Invoice. It's better to avoid the use of composite keys. You always have the option to use an auto generated hidden id. Although, sometimes you may need to connect to a legacy database or maybe the schema design has been done by someone that likes composite keys, and you must use composite keys albeit it's not the best choice. Therefore, here you will learn how to use a composite key, though at the end we'll change it to a single auto generated id.Let's start with a simple version of Invoice entity:
If you want to use year and number as a composite key for your Invoice, a way to do so is by marking year and number as @Id (shown as 2), and also to have an id class (shown as 1). The id class must have year and number as properties. You can see InvoiceKey here:
This code shows some of the requirements of a primary key class, such as to be serializable and to implement hashCode() and equals(). Eclipse can generate them for you with Source > Generate hashCode and equals()...
You already know how to use a composite key, and given that we have control over our schema, in the end we are going to use a UUID identifier for Invoice. Rewrite the Invoice entity to leave it as shown:
Also delete the InvoiceKey class. Using a hidden auto-generated key for Invoice class has several practical advantages over a composite key: You do not have to write the boring InvoiceKey class, you can modify the invoice number without losing any association from other objects and you can store in the same table the orders and invoices with the year/number repeated.
The code you have is enough to run the Invoice module. Do it and add some invoices if you want. Still a lot of work is remaining to be done in Invoice, like the default values for year, number and date.
Calculating default values
Currently the user needs to type the year, number and date in order to enter an invoice. It would be nice to have default values for them. It's easy to do it using the @DefaultValueCalculator annotation. In the next code you see how we can add the default values for year and date:Also you have to add the next import at the top of your class:
From now on when the user clicks on the 'new' button the year field will have the current year, and the date field the current date. These two calculators (CurrentYearCalculator and CurrentDateCalculator) are included in OpenXava. You can explore the org.openxava.calculators package to see other useful built-in calculators.
But sometimes you need your own logic for calculating the default value. For example, for number we want to add one to the last invoice number in the same year. Creating your own calculator with your logic is easy. First, create a package for calculators and call it org.openxava.invoicing.calculators. Then create in it a NextNumberForYearCalculator class, with the next code:
Your calculator must implement ICalculator interface (and therefore have a calculate() method). We declare a year property to put in the year of the calculation. To implement the logic we use a JPA query. You can learn how to use JPA in appendix B. Now we only have to annotate the number property in the Invoice entity:
Remember to add the import:
In this case you see something new, an annotation @PropertyValue. By using this annotation you're saying that the value of year property of the current Invoice will be moved to the property year of the calculator before doing the calculation. Now when ever the user clicks on 'new' the next invoice number is available for the year field. The way of calculating the invoice number is not the best for many concurrent users adding invoices. Don't worry, we'll improve this issue later on.
This is the visual effect of the default value calculators:
Default values are only the initial values. The user can change them if he wishes to.
Regular reference (ManyToOne)
Now that we have all atomic properties ready to use it's time to add relationships with other entities. We'll begin adding a reference from Invoice to Customer, because an invoice without customer is not very useful. Before adding the customer use the Invoice module to remove all the current invoices because we're going to make the customer required, so the old data could fail.Add the next code to the Invoice entity.
Nothing more is required. The Invoice module is now like this one:
There is no more work left here now. Let's add the collection of details to your Invoice.
Collection of dependent objects
Usually an invoice needs to have a couple of lines with the details of products, quantities, etc. These details are part of the invoice. They are not shared with other invoices, and when an invoice is deleted its details are also deleted. So, the more natural way of modeling the invoice details is to use a collection of embeddable objects. To do it with JPA, declare a regular collection annotated with @ElementCollection:Using @ElementCollection when the invoice is removed its details are removed too. The details are not saved in the database until the invoice is saved and they are saved all at once.
In order to make this collection work you need to write the Detail class:
Note that Detail is annotated with @Embeddable not with @Entity, you cannot define an @ElementCollection of entities. This @Embeddable class can contain properties and references but not collections.
At the moment we only have quantity and product and that is enough to get the Invoice running with details. The user can add, edit and remove elements from the collection just as in a spreadsheet:
This screenshot emphasizes that the properties to show by default in a collection are the plain ones, that is the properties of references are not included by default. This fact produces an ugly user interface for our collection of invoice details, because only the quantity property is shown. You can fix it using @ListProperties, in this way:
As you can see, you only have to feed the value for the annotation @ListProperties with the list of the properties you wish, separated by commas. You can use qualified properties, that is, to use the dot notation for accessing properties of references, such as product.number and product.description in this case. The visual result is:
Refining the user interface
Congratulations! You have finished your domain model classes, and you have an application running. Now the user can work with products, categories, customers and even create invoices. In the case of products, categories and customers the user interface is pretty decent, though the user interface for invoices still can be improved a little.By the way, you already have used some OpenXava annotations for refining the presentation, such as @DescriptionsList, @NoFrame and @ListProperties. In this section we'll use more of these annotations to give the Invoice user interface a better look without too much effort.
Default user interface
This is the default user interface for Invoice:
As you can see, OpenXava shows all the members, one per row, in the order you have declared them in the source code. Also, you can see how in the case of the customer reference the default view of the Customer is created.
We are going to do some improvements. First, we'll define the layout of the members explicitly. In this way we can put year, number and date in the same row. Second, we are going to use a simpler view for Customer. The user does not need to see all the data of the customer when he is entering the invoice.
Using @View for defining layout
For defining the layout of Invoice members in the user interface you have to use the @View annotation. It is easy because you only have to enumerate the members to be shown. Look at the code:At the end, we show all the members of Invoice, but we use commas to separate year, number and date. Thus they are in the same line, producing a more compact user interface, as this:
Using @ReferenceView to refine the user interface for reference
You still need to refine the way the customer reference is displayed, because it displays all the members of Customer, and for entering data for an Invoice, a simpler view of the customer may be better. To do so, you have to define a Simple view in Customer, and then indicate in Invoice that you want to use the Simple view of Customer to display it.First, let's define the Simple view in Customer:
When a view has a name, as in this case, then that view is only used when that name is specified. That is, though Customer has only this @View annotation, when you try to display a Customer it will not use this Simple view, but the one generated by default. If you define a @View with no name, that view will be the default one, though that is not the case here.
Now, you have to indicate that the reference to Customer from Invoice must use this Simple view. This is done by means of @ReferenceView:
Really simple, you only have to indicate the name of the view of the referenced entity you want to use.
After this the customer reference will be shown in a more compact way:
You can see below how you have refined your Invoice interface.
Refined user interface
This is the result of our refinements in the Invoice user interface:You have seen how easy it is to use @View and @ReferenceView to get a more compact user interface for Invoice.
Now you have a user interface good enough to start working, and you really have done little work to get it going.
Agile development
Nowadays agile development is no longer a “new and breaking technique”, but an established way to do software development, even the ideal way to go for many people.If you are not familiar with agile development you can have a look at www.agilemanifesto.org. Basically, agile development encourages the use of feedback from a working product over a careful upfront design. This gives a more prominent role to programmers and users, and minimizes the importance of analysts and software architects.
This type of development also needs a different type of tools. Because you need a working application rapidly. It must be as rapid to develop the initial application as it would be writing the functional description. Moreover, you need to respond to the user feedback quickly. The user needs to see his proposals running in short time.
OpenXava is ideal for agile development because not only does it allow a very rapid initial development, but it also allows you to make changes and see the effects instantly. Let's see a little example of this.
For example, once the user has looked at your application and starts to play with it, he takes into account that he works with books, music, software and so on. All these products have an author, and it would be useful to store the author, and see products by author.
Adding this new feature to your application is simple and rapid. First, create a new class for Author, with this:
Now, add the next code to the existing Product entity.
Thus, your Product entity has a reference to Author. Remember to generate the setter and setter for author.
Really you have written a little amount of code. In order to see the effect, you only need to build your project (just press Ctrl-B in your Eclipse), which is just immediate. Then go to the browser and reload the page with the Product module, and you will see there, a combo for choosing the author of the product, just as you see here:
What if the user wants to choose an author and see all his products? Well. This is plain vanilla. You only have to make the relationship between Product and Author bidirectional. Go to the Author class and add the next code:
To use Collection you have to add the next import:
Remember to generate the getter and setter for products. Now, press Ctrl+B (to build) and refresh the browser with Author module. Choose an author and you will see his products. You have to see something like this:
Yes, you added a new collection, refreshed your browser and there you get the full user interface to manage it. In this case when the user click on Add button he will be able to choose a book from a list of all existing books, moreover when the author is removed his books are still there in the database. You can define another behavior with cascade as REMOVE or ALL, thus:
In this way when your user try to add a new book a dialog will be open to entry all the data for the book and with a save button. Moreover, when the author would be removed his books will be removed too. For the author/books case we don't want this behavior, but it can be useful in many cases where the @ElementCollection is not enough.
In this section you have the complete code and steps required to do changes and see the result in the most interactive way. You have seen how OpenXava is an agile tool, ideal for doing agile development.
Summary
In this lesson you have learned how to use simple Java classes to create a Java Web application. With a mere few Java classes required to define your domain, you have a running application. Also, you have learned how to refine the default user interface using some OpenXava annotations. If you want to know all possible ways of refining the user interface with OpenXava look at the reference guide.Yes! Now you have a working application with little effort. Although this application “as is” can be useful as a CRUD utility or a prototype, you still need to add validations, business logic, user interface behavior, security and so on in order to convert these entities you have written into a business application ready for your user.
You will learn all these advanced topics in the forthcoming lessons.
Download source code of this lesson
Any problem with this lesson? Ask in the forum Everything fine? Go to Lesson 3